#include <stdint.h>
#include "dw_qspi.h"
#include "reg_common.h"

typedef struct {
    volatile uint32_t ctrlr0;
    volatile uint32_t ctrlr1;
    volatile uint32_t ssienr;
    volatile uint32_t mwcr;
    volatile uint32_t ser;
    volatile uint32_t baudr;
    volatile uint32_t txftlr;
    volatile uint32_t rxftlr;
    volatile uint32_t txflr;
    volatile uint32_t rxflr;
    volatile uint32_t sr;
    volatile uint32_t imr;
    volatile uint32_t isr;
    volatile uint32_t risr;
    volatile uint32_t txoicr;
    volatile uint32_t rxoicr;
    volatile uint32_t rxuicr;
    volatile uint32_t msticr;
    volatile uint32_t icr;
    volatile uint32_t dmacr;
    volatile uint32_t dmatdlr;
    volatile uint32_t dmardlr;
    volatile uint32_t idr;
    volatile uint32_t ssi_version_id;
    volatile uint32_t dr[36];
    volatile uint32_t rx_sample_dly;
    volatile uint32_t spi_ctrlr0;
    volatile uint32_t txd_drive_edge;
    volatile uint32_t rsvd;
} reg_ssi_t;

// #define FLASH_SSI           ((reg_ssi_t *)(0xff02c000))
#define FLASH_SSI           ((reg_ssi_t *)(0xD0013000))

static inline void ssi_simple_tx(uint8_t dat)
{
    // FLASH_SSI->ser = 0;
    FLASH_SSI->dr[0] = dat;
    // FLASH_SSI->ser = 1;
    while(FLASH_SSI->sr & 0x01) asm volatile ("fence.i");
    // delay needed, need to check wave why data error if no delay present.
//    for(volatile uint32_t i = 0; i < 10000; i++);   // delay
	asm volatile ("fence.i");
}

// the count must <= the fifo depth
static inline void ssi_transmit(uint8_t *tx_dat, uint8_t *rx_dat, uint8_t cnt)
{
    uint8_t tmp = cnt;
    
    // dummy read recv fifo
    while(FLASH_SSI->rxflr) {
        uint8_t dmy = FLASH_SSI->dr[0];
    }

    // prepare send data at first
    FLASH_SSI->ser = 0;
    while(tmp--) {
        FLASH_SSI->dr[0] = *tx_dat++;
        // printf("[QSPI_FLASH] qspim fifo level: tx->%u rx->%u\n",
        //                    FLASH_SSI->txflr, FLASH_SSI->rxflr);
    }
    // kick off and read out
    FLASH_SSI->ser = 1;
    while(cnt--) {
        while(!(FLASH_SSI->sr & 0x08));
        *rx_dat++ = FLASH_SSI->dr[0];
        //printf("FLASH_SSI->dr[0]=0x%x\n",FLASH_SSI->dr[0]));
    }
    while(FLASH_SSI->sr & 0x01)	asm volatile ("fence.i");

    // delay needed, need to check wave why data error if no delay present.
//    for(volatile uint32_t i = 0; i < 10000; i++);   // delay
		asm volatile ("fence.i");
}

static void flash_wait_busy(void)
{
    uint8_t cmd[3] = {0x05, 0, 0};
    uint8_t sta[3];
    do {
        ssi_transmit(cmd, sta, 3);
    } while(sta[1] & 0x01);
}

void flash_read(uint8_t *pdat, uint32_t start_addr, uint32_t cnt)
{
    uint8_t cmd[8];
    uint8_t dat[8];
    cmd[0] = 0x03;      // read data
    
    // we use fifo so do not need to touch interrupt or dma.
    // each time we readout 4 bytes.
    while(cnt >= 4) {
        cmd[1] = (start_addr & 0xff0000) >> 16;
        cmd[2] = (start_addr & 0x00ff00) >> 8;
        cmd[3] = (start_addr & 0x0000ff);
        ssi_transmit(cmd, dat, 8);
        *pdat++ = dat[4];
        *pdat++ = dat[5];
        *pdat++ = dat[6];
        *pdat++ = dat[7];
        cnt -= 4;
        start_addr += 4;
    }

    // remaining part
    if (cnt) {
        cmd[1] = (start_addr & 0xff0000) >> 16;
        cmd[2] = (start_addr & 0x00ff00) >> 8;
        cmd[3] = (start_addr & 0x0000ff);
        ssi_transmit(cmd, dat, cnt + 4);
        for (uint8_t i = 0; i < cnt; i++) {
            *pdat++ = dat[4 + i];
        }
    }
}

static void flash_write(const uint8_t *pdat, uint32_t start_addr, uint32_t cnt)
{
    // write enable first

    uint8_t cmd[8];
    uint8_t dmy[8];
    cmd[0] = 0x02;      // write data
    
    // we use fifo so do not need to touch interrupt or dma.
    // each time we readout 4 bytes.
    while(cnt >= 4) {
        cmd[1] = (start_addr & 0xff0000) >> 16;
        cmd[2] = (start_addr & 0x00ff00) >> 8;
        cmd[3] = (start_addr & 0x0000ff);
        cmd[4] = *pdat++;
        cmd[5] = *pdat++;
        cmd[6] = *pdat++;
        cmd[7] = *pdat++;
        ssi_simple_tx(0x06);
        ssi_transmit(cmd, dmy, 8);
        flash_wait_busy();
        cnt -= 4;
        start_addr += 4;
    }

    // remaining part
    if (cnt) {
        cmd[1] = (start_addr & 0xff0000) >> 16;
        cmd[2] = (start_addr & 0x00ff00) >> 8;
        cmd[3] = (start_addr & 0x0000ff);
        for (uint8_t i = 4; i < 4 + cnt; i++) {
            cmd[i] = *pdat++;
        }
        ssi_transmit(cmd, dmy, cnt + 4);
        flash_wait_busy();
    }
}

static int32_t flash_sector_erase(uint32_t sec)
{
    printf("[QSPI_FLASH] flash erase sector %u\n", sec);

    // write enable
    ssi_simple_tx(0x06);

    // erase sector
    uint8_t cmd[4];
    uint8_t dmy[4];
    cmd[0] = 0x20;  // sector erase
    cmd[1] = (sec & 0xff0000) >> 16;
    cmd[2] = (sec & 0x00ff00) >> 8;
    cmd[3] = (sec & 0x0000ff);
    ssi_transmit(cmd, dmy, 4);

    // busy check
    flash_wait_busy();

    // erase successfully?
    uint8_t dat[256];
    // flash_read(dat, 0, 256);
    // hex_data_dump(dat, 256);

    // // assert if data != 0xff
    // for (uint32_t i = 0; i < 256; i++) {
    //     if (dat[i] != 0xff) {
    //         printf("[QSPI_FLASH] flash erase sector failed.\n");
    //         return -1;
    //     }
    // }
    return 0;
}

static int32_t flash_program(void)
{
    printf("[QSPI_FLASH] flash program test\n");

    // generate test data
    uint8_t ran_data[256];
    uint8_t readback[256];
    for (uint32_t i = 0; i < 256; i++) {
        ran_data[i] = i;
    }

    flash_write(ran_data, 0, 256);
    // flash_read(readback, 0, 256);
    // printf("====== written data dump ======\n");
    // hex_data_dump(readback, 256);

    // for (uint32_t i = 0; i < 256; i++) {
    //     if (ran_data[i] != readback[i]) {
    //         printf("[QSPI_FLASH] flash program failed.\n");
    //         return -1;
    //     }
    // }
    return 0;
}

int32_t flash_read_test(void)
{
    printf("[QSPI_FLASH] flash read test\n");

    // generate test data
    uint8_t readback[256];

    flash_read(readback, 0x00, 256);
    printf("====== readback data dump ======\r\n");
    hex_data_dump(readback, 256);
    
    return 0;
}

int32_t qspi_flash_manual_mode_init(void)
{
    // // switch to manual mode

    // qspim base address: STATION_FLASH_MANUAL_MODE_BLOCK_REG_ADDR
    // we can check ssi_version_id to ensure ssi is present...s
    uint32_t ver = FLASH_SSI->ssi_version_id;
    printf("[QSPI_FLASH] qspim version id = %x\r\n", ver);
    if (ver == 0) {
        printf("[QSPI_FLASH] ssi module version read error. exit -1\r\n");
        return -1;
    }

    // ssi init
   FLASH_SSI->ssienr =  0;
   FLASH_SSI->ctrlr0 = (0x07 << 16) + 0x07;
   FLASH_SSI->baudr  = 8;
   FLASH_SSI->ssienr = 1;
   FLASH_SSI->ser    = 0x01;

    // flash init
    ssi_simple_tx(0xab);    // low power out
    ssi_simple_tx(0x66);    // flash reset enable
    ssi_simple_tx(0x99);    // flash reset
    uint8_t jedec_id_cmd[4] = {0x9f, 0, 0, 0};
    uint8_t jedec_id[4];
    ssi_transmit(jedec_id_cmd, jedec_id, 4);
    printf("[QSPI_FLASH] flash jedec id = %x %x %x\r\n", jedec_id[1], jedec_id[2], jedec_id[3]);
    // wrong winbond w25q128jv jedec id
    if (jedec_id[1] != 0x1f) {
        printf("[QSPI_FLASH] flash jedec id read error. exit -1\r\n");
        return -1;
    }

    return 0;
}

int32_t qspi_flash_test(void)
{
    int32_t ret;
    ret = qspi_flash_manual_mode_init();
    flash_read_test();
    if (ret)    return ret;    
}
